# Kestrel: An XMPP-based Many-Task Computing Scheduler
# Author: Lance Stout <lancestout@gmail.com>
#
# Copyright 2009-2010 Clemson University
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from kestrel.core.datatypes import Task


class Policy(object):

    def __init__(self, kernel):
        """
        The policy module provides the logic for choosing to handle
        or deny any requests independent of how those requests
        would actually be processed.

        Configuration entries used by this module:
          system/status

        Events listened for by this module:
          task/received
          task/cancel
          task/cleaned
          worker/start
          profile/approved

        Events raised by this module:
          task/rejected
          task/approved
          task/cancelled
          system/available
          system/busy
        """

        self.kernel = kernel
        self.events = {'worker/start': self.on_start,
                       'task/received': self.approve_task,
                       'task/cancel': self.approve_cancellation,
                       'task/cleaned': self.after_task,
                       'profile/approved': self.profile_approved}
        self.kernel.register(self)

    def on_start(self, data=None):
        """
        Request access to an agent with a manager role.
        """
        manager = self.kernel.config('worker/manager/id')
        if manager is not None:
            self.kernel.event('system/access/request', manager)

    def approve_task(self, task_msg):
        """Analyzes and approves or rejects tasks for execution."""
        task = Task(task_msg)

        # If the task contains errors:
        # self.kernel.event("task/malformed")

        # If task is not from an approved source,
        # or is against current policy,
        # or a task is already being executed:
        # self.kernel.event("task/rejected", task)
        if self.kernel.config('system/status') != 'available':
            self.kernel.event('task/rejected', task)
            return

        # Otherwise:

        # In the future, it could be possible to allow multiple
        # tasks to run concurrently. In such a case, only when the
        # number of running jobs is equal to the maximum allowed
        # would the system/busy event fire.
        self.kernel.event('system/busy')
        self.kernel.event("task/approved", task)

    def approve_cancellation(self, msg):
        """Check that a cancellation notice is valid."""
        # If the cancellation notice is from task's source
        # and it is for the same task ID, then cancel.
        self.kernel.event('task/cancelled')

    def after_task(self, task):
        """Return the worker to a ready state for more tasks."""
        self.kernel.event('system/available')

    def profile_approved(self, data=None):
        """
        Perform actions after the manager has accepted
        the worker's profile.

        Currently this is just resending a presence notification
        to the manager.
        """
        self.kernel.event('system/available')
